package com.yirui.bridge.tcp;


import com.yirui.bridge.core.Server;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioChannelOption;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;

import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;


/**
 * @author Dujie
 * @create 2021/11/3
 * @since 1.0.0
 */
@Slf4j
public class NettyTcpClient implements Server {
    private String host;
    private int port;
    private Channel channel;
    private int connectionTimeout;
    private EventLoopGroup eventLoopGroup;
    private TcpChannelInitializer channelChannelInitializer;


    /**
     * 重新连接开始时间
     */
    private long reconnectTime;

    private String status;

    private boolean isStart = false;
    private Bootstrap bootstrap = new Bootstrap();

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public long getReconnectTime() {
        return reconnectTime;
    }

    public void setReconnectTime(long reconnectTime) {
        this.reconnectTime = reconnectTime;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public boolean isStart() {
        return isStart;
    }

    public void setStart(boolean start) {
        isStart = start;
    }

    private void start(String host, int port, int connectionTimeout) throws Exception {
        this.host = host;
        this.port = port;
        this.connectionTimeout = connectionTimeout;
        log.info("TCP连接目标服务地址Host:{} port:{} connectionTimeout:{}", host, port, connectionTimeout);


        bootstrap.group(eventLoopGroup)
                // 使用NioSocketChannel来作为连接用的channel类
                .channel(NioSocketChannel.class)

                 .option(NioChannelOption.SO_REUSEADDR, true)
                .option(NioChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.TCP_NODELAY, true)
                //.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectionTimeout())
                .handler(channelChannelInitializer);


        bootstrap.remoteAddress(new InetSocketAddress(host, port));
        connect();

        //发起异步连接请求，绑定连接端口和host信息

        //while(!connect) {
            /*try {
                final ChannelFuture future = b.connect(host, port).sync();

                future.addListener((ChannelFutureListener) arg0 -> {
                    if (future.isSuccess()) {
                        log.info("连接服务器成功");

                    } else {
                        log.error("连接服务器失败");
                        future.cause().printStackTrace();
                        eventLoopGroup.shutdownGracefully(); //关闭线程组
                    }
                });
                this.channel = future.channel();
                connect = true;
            } catch (Exception ex) {
                ex.printStackTrace();
                Thread.sleep(1000);
            }*/
        //}





    }

    public void connect() throws Exception {
        log.info("Netty TCP client to connect {}:{}", host, port);
        //启动客户端去连接服务器端
        ChannelFuture future = bootstrap.connect(host, port);
        future.addListener((ChannelFutureListener) future1 -> {
            if (!future1.isSuccess()) {
                //重连交给后端线程执行
                future1.channel().eventLoop().schedule(() -> {
                    log.info("连接服务端失败，8s后重连...");
                    try {
                        connect();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }, 8000, TimeUnit.MILLISECONDS);
            } else {
                log.info("建立连接，连接服务端成功！");

                isStart = true;
                channel = future1.channel();
            }
        });
        //对通道关闭进行监听
        try {
            future.channel().closeFuture().sync();
        }catch (Exception ex){
            log.error("Try connect to {}:{} exception:{}", host, port, ex.getMessage());
        }
    }

//    private void doConnect() {
//        bootstrap.connect().addListener((ChannelFuture f) -> {
//            if (!f.isSuccess()) {
//                log.info("连接服务端失败，5s后重连...");
//                final EventLoop loop = f.channel().eventLoop();
//                loop.schedule(() -> doConnect(), 5L, TimeUnit.SECONDS);
//            } else {
//                log.info("建立连接，连接服务端成功！");
//                isStart = true;
//                channel = f.channel();
//            }
//        });
//    }

    private int getConnectionTimeout() {
        return connectionTimeout;
    }

    public Channel getChannel() {
        return channel;
    }

    public void stop() {
        log.debug("Client stop.");
        try {
            if (!isStart) {
                return;
            }

            log.info("关闭连接！");
            eventLoopGroup.shutdownGracefully().sync();
            isStart = false;
        } catch (Exception e) {
            e.printStackTrace();
        }

       /* try {
            if (channel != null && channel.isActive()) {

                ChannelFuture await = channel.close().await();
                if (!await.isSuccess()) {
                    log.error("Client channel close fail, {}", await.cause());
                }
            }
            Future<?> future1 = eventLoopGroup.shutdownGracefully().await();
            if (!future1.isSuccess()) {
                log.error("Client group shutdown fail, {}", future1.cause());
            }
            log.info("Client shutdown success");
        } catch (InterruptedException e) {
            log.info("Client shutdown fail {}", e.getMessage());
        }*/
    }

    public void setEventLoopGroup(NioEventLoopGroup eventLoopGroup) {
        this.eventLoopGroup = eventLoopGroup;
    }

    public void setChannelInitializer(TcpChannelInitializer channelChannelInitializer) {
        this.channelChannelInitializer = channelChannelInitializer;
        this.channelChannelInitializer.setClient(this);
    }


    @Override
    public void start(String host, int port, ApplicationArguments args) throws Exception {

        String ct = null;
        if(args.containsOption("c_timeout")){
            ct = args.getOptionValues("ct").get(0);
        }
        int connectTimeout = Integer.parseInt(ct == null?"10000":ct);

        setChannelInitializer(new TcpChannelInitializer(new TcpChannelInboundHandler()));
        setEventLoopGroup(new NioEventLoopGroup());
        start(host,port, connectionTimeout);
    }
}
